/*
 * Created by Jaren at 2022/1/29 11:09
 */
import 'dart:async';
import 'dart:core';
import 'dart:io';
import 'dart:typed_data';
import 'package:inmotion_protocol/base/file/file_manager.dart';
import 'package:inmotion_protocol/base/protocol.dart';
import 'package:inmotion_protocol/lorin/constant.dart';
import 'package:inmotion_protocol/lorin/file/lorin_file_manager.dart';
import 'package:inmotion_protocol/lorin/file/response.dart';
import 'package:inmotion_protocol/lorin/lorin_message.dart';
import 'package:inmotion_protocol/protocol_worker.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/log.dart';

class LorinFileUploadManager extends LorinFileManager {
  LorinFileUploadManager(
      Protocol<LorinMessage> protocol, LorinFileManagerConfig config)
      : super(protocol, config);

  @override
  Future<bool> checkFile(int target, int fileHandle, int offset, int length,
      Uint8ClampedList expectCheck) {
    return protocol
        .checkFile(
            target: target,
            fileHandle: fileHandle,
            offset: offset,
            length: length,
            check: expectCheck)
        .then((fileCheckRsp) {
      fileCheckRsp as CheckFileResponse;

      if (!fileCheckRsp.isSuccess()) {
        throw FileOperationStepError(
            IFileManager.stepCheckFile, fileCheckRsp.originError);
      }
      context.retryTimes = 0;
      if (!fileCheckRsp.isCheckSuccess()) {
        // 校验失败时，关闭文件，重试
        if (context.checkRetryTimes > 0) {
          throw FileOperationStepError.withExtra(IFileManager.stepCheckFile,
              fileCheckRsp.originError, fileCheckRsp.checkResult);
        }
        context.checkRetryTimes++;
        return closeFile(target, fileHandle).then((closeRsp) {
          return Future.delayed(const Duration(milliseconds: 500), () {
            return openFile().then((openRsp) {
              context.fileHandle = openRsp.fileHandle;
              return checkFile(
                  target, openRsp.fileHandle, offset, length, expectCheck);
            });
          });
        });
      }
      context.checkRetryTimes = 0;
      return Future.value(true);
    }).catchError((error) {
      if (error is MsgTimeoutException) {
        if (context.retryTimes < maxRetryTimes()) {
          context.retryTimes++;
          return checkFile(target, fileHandle, offset, length, expectCheck);
        }
      }
      if (error is FileOperationStepError) {
        throw error;
      }
      throw FileOperationStepError(
          IFileManager.stepCheckFile, IFileManager.errorUnknown);
    });
  }

  @override
  Future<bool> prepareTransfer(
      int remoteFileLength,
      Uint8ClampedList remoteCheck,
      File localFile,
      StreamController progressTicker) {
    if (remoteFileLength == 0) {
      // 设备端已存在文件大小为0，从头开始传输
      return transferFile(
          context, localFile, 0, remoteFileLength, progressTicker);
    }
    // 设备端文件已存在，判断校验是否一致，是否可以续传
    return checkCrc32(remoteCheck, localFile, remoteFileLength)
        .then((checkResult) {
      if (checkResult) {
        // 校验一致，可以续传
        return transferFile(context, localFile, remoteFileLength,
            remoteFileLength, progressTicker);
      }
      // 校验不一致，从头开始传输
      return transferFile(
          context, localFile, 0, remoteFileLength, progressTicker);
    });
  }

  @override
  Future<bool> transferFile(Context context, File file, int transferOffset,
      int remoteFileLength, StreamController progressTicker) {
    return file.readAsBytes().then((fileBytes) {
      context.fileBytes = fileBytes;
      context.fileLength = fileBytes.length;
      return transferFileImpl(
          context, transferOffset, 1, fileBytes, progressTicker, 0);
    }).catchError((error) {
      if (error is IOException) {
        progressTicker.addError(FileOperationStepError(
            IFileManager.stepOpenFile, IFileManager.errorSendFileIOError));
        return Future.value(false);
      }
      throw error;
    });
  }

  Future<bool> transferFileImpl(Context context, int initOffset, int tab,
      Uint8List fileBytes, StreamController progressTicker, int index) {
    int totalSize = fileBytes.length;
    Uint8ClampedList pkg = subNextPkgBytes(fileBytes, initOffset, totalSize,
        context.target == Constant.deviceScreen ? 128 : sizePeerPkg());
    if (pkg.isEmpty) {
      progressTicker.add(FileProgressEvent(
          initOffset,
          totalSize,
          calAveSpeed(),
          calRtSpeed(),
          now() - context.startTransferTimestamp,
          calRemainSeconds()));
      return Future.value(true);
    }
    return transferFileImpl0(
            context.target, context.fileHandle!, initOffset, tab, pkg, index)
        .then((none) {
      if (!none) return Future(() => false);
      index++;
      if (checkInterrupted()) throw CanceledError();

      onTransferring(initOffset, pkg.length, context.fileLength);

      int nextOffset = initOffset + pkg.length;
      // trigger
      progressTicker.add(FileProgressEvent(
          nextOffset,
          totalSize,
          calAveSpeed(),
          calRtSpeed(),
          now() - context.startTransferTimestamp,
          calRemainSeconds()));

      if (nextPkgSize(nextOffset, totalSize, sizePeerPkg()) <= 0) {
        return Future.value(true);
      }
      return transferFileImpl(
          context, nextOffset, tab + 1, fileBytes, progressTicker, index);
    });
  }

  Future<bool> transferFileImpl0(int target, int fileHandle, int offset,
      int tab, Uint8ClampedList data, int index) {
    if (context.target == Constant.deviceScreen) {
      print("✅✅✅✅ = 开始屏幕升级 - 发送 index = $index");
      Uint8ClampedList para = data;
      // 发送下载指令
      List<int> list = [0xEE, 0xB6];
      Uint8ClampedList length = ByteUtil.num2Bytes(para.length + 5, 2);
      list.addAll(length);
      list.addAll([0x88, 0x22]);
      list.addAll([index % 256]);
      list.addAll(para);
      int checksum = 0;
      for (int i = 0; i < list.length; i++) {
        if (i != 0) {
          checksum += list[i];
        }
      }
      list.addAll(ByteUtil.num2Bytes(checksum ^ 0xFFFF, 2));
      list.addAll([0xFF, 0xFC, 0xFF, 0xFF]);
      Future<bool> write() {
        return protocol
            .startTransfer(target, para: Uint8ClampedList.fromList(list))
            .then((rsp) {
          return Future.value(rsp.isSuccess());
        });
      }

      return write().then((rsp) {
        if (rsp) {
          return Future.value(true);
        } else {
          return write();
        }
      });
    } else {
      return protocol
          .writeFile(data,
              fileHandle: fileHandle, offset: offset, tab: tab, target: target)
          .then((rsp) {
        if (rsp.isSuccess()) {
          return Future.value(true);
        }
        throw FileOperationStepError.withExtra(
            IFileManager.stepTransferFile, rsp.originError, offset);
      }).catchError((error) {
        LogUtils.e(error.toString());
        if (error is FileOperationStepError) {
          throw error;
        }
        if (error is MsgTimeoutException) {
          throw FileOperationStepError.withExtra(
              IFileManager.stepTransferFile, IFileManager.errorTimeout, offset);
        }
        throw FileOperationStepError.withExtra(
            IFileManager.stepTransferFile, IFileManager.errorUnknown, offset);
      });
    }
  }
}
